#!/usr/bin/env python
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""code generator for raster command buffers."""

import filecmp
import os
import os.path
import sys
from optparse import OptionParser

import build_cmd_buffer_lib

# Named type info object represents a named type that is used in OpenGL call
# arguments.  Each named type defines a set of valid OpenGL call arguments.  The
# named types are used in 'raster_cmd_buffer_functions.txt'.
# type: The actual GL type of the named type.
# valid: The list of values that are valid for both the client and the service.
# invalid: Examples of invalid values for the type. At least these values
#          should be tested to be invalid.
# is_complete: The list of valid values of type are final and will not be
#              modified during runtime.
# validator: If set to False will prevent creation of a ValueValidator. Values
#            are still expected to be checked for validity and will be tested.
_NAMED_TYPE_INFO = {
  'GLState': {
    'type': 'GLenum',
    'valid': [
      'GL_ACTIVE_TEXTURE',
    ],
    'invalid': [
      'GL_FOG_HINT',
    ],
  },
  'QueryObjectParameter': {
    'type': 'GLenum',
    'is_complete': True,
    'valid': [
      'GL_QUERY_RESULT_EXT',
      'GL_QUERY_RESULT_AVAILABLE_EXT',
      'GL_QUERY_RESULT_AVAILABLE_NO_FLUSH_CHROMIUM_EXT',
    ],
  },
  'QueryTarget': {
    'type': 'GLenum',
    'is_complete': True,
    'valid': [
      'GL_COMMANDS_ISSUED_CHROMIUM',
      'GL_COMMANDS_ISSUED_TIMESTAMP_CHROMIUM',
      'GL_COMMANDS_COMPLETED_CHROMIUM',
    ],
    'invalid': [
      'GL_LATENCY_QUERY_CHROMIUM',
    ],
  },
  'TextureParameter': {
    'type': 'GLenum',
    'valid': [
      'GL_TEXTURE_MAG_FILTER',
      'GL_TEXTURE_MIN_FILTER',
      'GL_TEXTURE_WRAP_S',
      'GL_TEXTURE_WRAP_T',
    ],
    'invalid': [
      'GL_GENERATE_MIPMAP',
    ],
  },
  'TextureWrapMode': {
    'type': 'GLenum',
    'valid': [
      'GL_CLAMP_TO_EDGE',
    ],
    'invalid': [
      'GL_REPEAT',
    ],
  },
  'TextureMinFilterMode': {
    'type': 'GLenum',
    'valid': [
      'GL_NEAREST',
    ],
    'invalid': [
      'GL_NEAREST_MIPMAP_NEAREST',
    ],
  },
  'TextureMagFilterMode': {
    'type': 'GLenum',
    'valid': [
      'GL_NEAREST',
    ],
    'invalid': [
      'GL_LINEAR',
    ],
  },
  'ResetStatus': {
    'type': 'GLenum',
    'is_complete': True,
    'valid': [
      'GL_GUILTY_CONTEXT_RESET_ARB',
      'GL_INNOCENT_CONTEXT_RESET_ARB',
      'GL_UNKNOWN_CONTEXT_RESET_ARB',
    ],
  },
  'gfx::BufferUsage': {
    'type': 'gfx::BufferUsage',
    'valid': [
      'gfx::BufferUsage::GPU_READ',
      'gfx::BufferUsage::SCANOUT',
      'gfx::BufferUsage::GPU_READ_CPU_READ_WRITE',
    ],
    'invalid': [
      'gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE',
      'gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE',
    ],
  },
  'viz::ResourceFormat': {
    'type': 'viz::ResourceFormat',
    'valid': [
      'viz::ResourceFormat::RGBA_8888',
      'viz::ResourceFormat::RGBA_4444',
      'viz::ResourceFormat::BGRA_8888',
      'viz::ResourceFormat::ALPHA_8',
      'viz::ResourceFormat::LUMINANCE_8',
      'viz::ResourceFormat::RGB_565',
      'viz::ResourceFormat::BGR_565',
      'viz::ResourceFormat::RED_8',
      'viz::ResourceFormat::RG_88',
      'viz::ResourceFormat::LUMINANCE_F16',
      'viz::ResourceFormat::RGBA_F16',
      'viz::ResourceFormat::R16_EXT',
      'viz::ResourceFormat::RGBX_8888',
      'viz::ResourceFormat::BGRX_8888',
      'viz::ResourceFormat::RGBA_1010102',
      'viz::ResourceFormat::BGRA_1010102',
      'viz::ResourceFormat::YVU_420',
      'viz::ResourceFormat::YUV_420_BIPLANAR',
      'viz::ResourceFormat::P010',
    ],
    'invalid': [
      'viz::ResourceFormat::ETC1',
    ],
  },
  'gpu::raster::MsaaMode': {
    'type': 'gpu::raster::MsaaMode',
    'is_complete': True,
    'valid': [
      'gpu::raster::MsaaMode::kNoMSAA',
      'gpu::raster::MsaaMode::kMSAA',
      'gpu::raster::MsaaMode::kDMSAA',
    ],
  },
}

# A function info object specifies the type and other special data for the
# command that will be generated. A base function info object is generated by
# parsing the "raster_cmd_buffer_functions.txt", one for each function in the
# file. These function info objects can be augmented and their values can be
# overridden by adding an object to the table below.
#
# Must match function names specified in "raster_cmd_buffer_functions.txt".
#
# type:         defines which handler will be used to generate code.
# decoder_func: defines which function to call in the decoder to execute the
#               corresponding GL command. If not specified the GL command will
#               be called directly.
# cmd_args:     The arguments to use for the command. This overrides generating
#               them based on the GL function arguments.
# data_transfer_methods: Array of methods that are used for transfering the
#               pointer data.  Possible values: 'immediate', 'shm', 'bucket'.
#               The default is 'immediate' if the command has one pointer
#               argument, otherwise 'shm'. One command is generated for each
#               transfer method. Affects only commands which are not of type
#               'GETn' or 'GLcharN'.
#               Note: the command arguments that affect this are the final args,
#               taking cmd_args override into consideration.
# impl_func:    Whether or not to generate the GLES2Implementation part of this
#               command.
# internal:     If true, this is an internal command only, not exposed to the
#               client.
# count:        The number of units per element. For PUTn or PUT types.
# use_count_func: If True the actual data count needs to be computed; the count
#               argument specifies the maximum count.
# unit_test:    If False no service side unit test will be generated.
# client_test:  If False no client side unit test will be generated.
# expectation:  If False the unit test will have no expected calls.
# valid_args:   A dictionary of argument indices to args to use in unit tests
#               when they can not be automatically determined.
# invalid_test: False if no invalid test needed.
# not_shared:   For GENn types, True if objects can't be shared between contexts

_FUNCTION_INFO = {
  'CopySubTextureINTERNAL': {
    'decoder_func': 'DoCopySubTextureINTERNAL',
    'internal': True,
    'type': 'PUT',
    'count': 32,  # GL_MAILBOX_SIZE_CHROMIUM x2
    'unit_test': False,
    'trace_level': 2,
  },
  'WritePixelsINTERNAL': {
    'decoder_func': 'DoWritePixelsINTERNAL',
    'internal': True,
    'type': 'PUT',
    'count': 16,  # GL_MAILBOX_SIZE_CHROMIUM
    'unit_test': False,
    'trace_level': 2,
  },
  'ReadbackARGBImagePixelsINTERNAL': {
    'decoder_func': 'DoReadbackARGBImagePixelsINTERNAL',
    'internal': True,
    'type': 'PUT',
    'count': 16,  # GL_MAILBOX_SIZE_CHROMIUM
    'unit_test': False,
    'result': ['uint32_t'],
    'trace_level': 2,
  },
  'ReadbackYUVImagePixelsINTERNAL': {
    'decoder_func': 'DoReadbackYUVImagePixelsINTERNAL',
    'internal': True,
    'type': 'PUT',
    'count': 16, # GL_MAILBOX_SIZE_CHROMIUM
    'unit_test': False,
    'result': ['uint32_t'],
    'trace_level': 2,
  },
  'ConvertYUVAMailboxesToRGBINTERNAL': {
    'decoder_func': 'DoConvertYUVAMailboxesToRGBINTERNAL',
    'internal': True,
    'type': 'PUT',
    'count': 80, #GL_MAILBOX_SIZE_CHROMIUM x5
    'unit_test': False,
    'trace_level': 2,
  },
  'ConvertRGBAToYUVAMailboxesINTERNAL': {
    'decoder_func': 'DoConvertRGBAToYUVAMailboxesINTERNAL',
    'internal': True,
    'type': 'PUT',
    'count': 80, #GL_MAILBOX_SIZE_CHROMIUM x5
    'unit_test': False,
    'trace_level': 2,
  },
  'Finish': {
    'impl_func': False,
    'client_test': False,
    'decoder_func': 'DoFinish',
    'unit_test': False,
    'trace_level': 1,
  },
  'Flush': {
    'impl_func': False,
    'decoder_func': 'DoFlush',
    'unit_test': False,
    'trace_level': 1,
  },
  'GetError': {
    'type': 'Is',
    'decoder_func': 'GetErrorState()->GetGLError',
    'impl_func': False,
    'result': ['GLenum'],
    'client_test': False,
  },
  'GetGraphicsResetStatusKHR': {
    'type': 'NoCommand',
    'trace_level': 1,
  },
  'GenQueriesEXT': {
    'type': 'GENn',
    'gl_test_func': 'glGenQueriesARB',
    'resource_type': 'Query',
    'resource_types': 'Queries',
    'unit_test': False,
    'not_shared': 'True',
  },
  'DeleteQueriesEXT': {
    'type': 'DELn',
    'gl_test_func': 'glDeleteQueriesARB',
    'resource_type': 'Query',
    'resource_types': 'Queries',
    'unit_test': False,
  },
  'BeginQueryEXT': {
    'type': 'Custom',
    'impl_func': False,
    'cmd_args': 'GLenumQueryTarget target, GLidQuery id, void* sync_data',
    'data_transfer_methods': ['shm'],
    'gl_test_func': 'glBeginQuery',
  },
  'EndQueryEXT': {
    'type': 'Custom',
    'impl_func': False,
    'cmd_args': 'GLenumQueryTarget target, GLuint submit_count',
    'gl_test_func': 'glEndnQuery',
    'client_test': False,
  },
  'QueryCounterEXT' : {
    'type': 'Custom',
    'impl_func': False,
    'cmd_args': 'GLidQuery id, GLenumQueryTarget target, '
                'void* sync_data, GLuint submit_count',
    'data_transfer_methods': ['shm'],
    'gl_test_func': 'glQueryCounter',
  },
  'GetQueryObjectuivEXT': {
    'type': 'NoCommand',
    'gl_test_func': 'glGetQueryObjectuiv',
  },
  'GetQueryObjectui64vEXT': {
    'type': 'NoCommand',
    'gl_test_func': 'glGetQueryObjectui64v',
  },
  'OrderingBarrierCHROMIUM': {
    'type': 'NoCommand',
  },
  'TraceBeginCHROMIUM': {
    'type': 'Custom',
    'impl_func': False,
    'client_test': False,
    'cmd_args': 'GLuint category_bucket_id, GLuint name_bucket_id',
    'extension': 'CHROMIUM_trace_marker',
  },
  'TraceEndCHROMIUM': {
    'impl_func': False,
    'client_test': False,
    'decoder_func': 'DoTraceEndCHROMIUM',
    'unit_test': False,
    'extension': 'CHROMIUM_trace_marker',
  },
  'SetActiveURLCHROMIUM': {
    'type': 'Custom',
    'impl_func': False,
    'client_test': False,
    'cmd_args': 'GLuint url_bucket_id',
  },
  'LoseContextCHROMIUM': {
    'decoder_func': 'DoLoseContextCHROMIUM',
    'unit_test': False,
    'trace_level': 1,
  },
  'BeginRasterCHROMIUM': {
    'decoder_func': 'DoBeginRasterCHROMIUM',
    'type': 'PUT',
    'count': 16,  # GL_MAILBOX_SIZE_CHROMIUM
    'internal': True,
    'impl_func': False,
    'unit_test': False,
  },
  'RasterCHROMIUM': {
    'decoder_func': 'DoRasterCHROMIUM',
    'internal': True,
    'impl_func': True,
    'cmd_args': 'GLuint raster_shm_id, GLuint raster_shm_offset,'
                'GLsizeiptr raster_shm_size, GLuint font_shm_id,'
                'GLuint font_shm_offset, GLsizeiptr font_shm_size',
    'extension': 'CHROMIUM_raster_transport',
    'extension_flag': 'chromium_raster_transport',
  },
  'EndRasterCHROMIUM': {
    'decoder_func': 'DoEndRasterCHROMIUM',
    'impl_func': False,
    'unit_test': False,
    'client_test': False,
  },
  'CreateTransferCacheEntryINTERNAL': {
    'decoder_func': 'DoCreateTransferCacheEntryINTERNAL',
    'cmd_args': 'GLuint entry_type, GLuint entry_id, GLuint handle_shm_id, '
                'GLuint handle_shm_offset, GLuint data_shm_id, '
                'GLuint data_shm_offset, GLuint data_size',
    'internal': True,
    'impl_func': True,
    'client_test': False,
    'unit_test': False,
  },
  'DeleteTransferCacheEntryINTERNAL': {
    'decoder_func': 'DoDeleteTransferCacheEntryINTERNAL',
    'cmd_args': 'GLuint entry_type, GLuint entry_id',
    'internal': True,
    'impl_func': True,
    'client_test': False,
    'unit_test': False,
  },
  'DeletePaintCacheTextBlobsINTERNAL': {
    'type': 'DELn',
    'internal': True,
    'unit_test': False,
  },
  'DeletePaintCachePathsINTERNAL': {
    'type': 'DELn',
    'internal': True,
    'unit_test': False,
  },
  'ClearPaintCacheINTERNAL': {
    'decoder_func': 'DoClearPaintCacheINTERNAL',
    'internal': True,
    'unit_test': False,
  },
  'UnlockTransferCacheEntryINTERNAL': {
    'decoder_func': 'DoUnlockTransferCacheEntryINTERNAL',
    'cmd_args': 'GLuint entry_type, GLuint entry_id',
    'internal': True,
    'impl_func': True,
    'client_test': False,
    'unit_test': False,
  },
}


def main(argv):
  """This is the main function."""
  parser = OptionParser()
  parser.add_option(
      "--output-dir",
      help="Output directory for generated files. Defaults to chromium root "
      "directory.")
  parser.add_option(
      "-v", "--verbose", action="store_true", help="Verbose logging output.")
  parser.add_option(
      "-c", "--check", action="store_true",
      help="Check if output files match generated files in chromium root "
      "directory.  Use this in PRESUBMIT scripts with --output-dir.")

  (options, _) = parser.parse_args(args=argv)

  # This script lives under src/gpu/command_buffer.
  script_dir = os.path.dirname(os.path.abspath(__file__))
  assert script_dir.endswith(os.path.normpath("src/gpu/command_buffer"))
  # os.path.join doesn't do the right thing with relative paths.
  chromium_root_dir = os.path.abspath(script_dir + "/../..")

  # Support generating files under gen/ and for PRESUBMIT.
  if options.output_dir:
    output_dir = options.output_dir
  else:
    output_dir = chromium_root_dir
  os.chdir(output_dir)

  build_cmd_buffer_lib.InitializePrefix("Raster")
  gen = build_cmd_buffer_lib.GLGenerator(
      options.verbose, "2018", _FUNCTION_INFO, _NAMED_TYPE_INFO,
      chromium_root_dir)
  gen.ParseGLH("gpu/command_buffer/raster_cmd_buffer_functions.txt")

  gen.WriteCommandIds("gpu/command_buffer/common/raster_cmd_ids_autogen.h")
  gen.WriteFormat("gpu/command_buffer/common/raster_cmd_format_autogen.h")
  gen.WriteFormatTest(
    "gpu/command_buffer/common/raster_cmd_format_test_autogen.h")
  gen.WriteGLES2InterfaceHeader(
    "gpu/command_buffer/client/raster_interface_autogen.h")
  gen.WriteGLES2ImplementationHeader(
    "gpu/command_buffer/client/raster_implementation_autogen.h")
  gen.WriteGLES2Implementation(
    "gpu/command_buffer/client/raster_implementation_impl_autogen.h")
  gen.WriteGLES2ImplementationUnitTests(
    "gpu/command_buffer/client/raster_implementation_unittest_autogen.h")
  gen.WriteCmdHelperHeader(
     "gpu/command_buffer/client/raster_cmd_helper_autogen.h")
  gen.WriteServiceImplementation(
    "gpu/command_buffer/service/raster_decoder_autogen.h")
  gen.WriteServiceUnitTests(
    "gpu/command_buffer/service/raster_decoder_unittest_%d_autogen.h")
  gen.WriteServiceUtilsHeader(
    "gpu/command_buffer/service/raster_cmd_validation_autogen.h")
  gen.WriteServiceUtilsImplementation(
    "gpu/command_buffer/service/"
    "raster_cmd_validation_implementation_autogen.h")

  build_cmd_buffer_lib.Format(gen.generated_cpp_filenames, output_dir,
                              chromium_root_dir)

  if gen.errors > 0:
    print "build_raster_cmd_buffer.py: Failed with %d errors" % gen.errors
    return 1

  check_failed_filenames = []
  if options.check:
    for filename in gen.generated_cpp_filenames:
      if not filecmp.cmp(os.path.join(output_dir, filename),
                         os.path.join(chromium_root_dir, filename)):
        check_failed_filenames.append(filename)

  if len(check_failed_filenames) > 0:
    print 'Please run gpu/command_buffer/build_raster_cmd_buffer.py'
    print 'Failed check on autogenerated command buffer files:'
    for filename in check_failed_filenames:
      print filename
    return 1

  return 0


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))
